package com.lzj.common.security.service;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import com.lzj.common.constant.CacheConstants;
import com.lzj.common.constant.Constants;
import com.lzj.common.core.domain.model.LoginUser;
import com.lzj.common.utils.ServletUtils;
import com.lzj.common.utils.ip.AddressUtils;
import com.lzj.common.utils.redis.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

import static com.lzj.common.utils.StringUtils.getValueWithFallbacks;

/**
 * @author 贱贱
 * @Description token服务
 * @Date 2024/07/13 00:07
 */
@Slf4j
@Service
public class TokenService {

    // 令牌自定义标识
    @Value("${token.header:Authorization}")
    private String header;

    // 令牌秘钥
    @Value("${token.secret:abcdefg}")
    private String secret;

    // 令牌有效期
    @Value("${token.expireTime:60}")
    private int expireTime;
    

    /**
     * 从请求中获取用户信息
     */
    public LoginUser getLoginUser(HttpServletRequest request) {
        //从请求中取出token
        String token = getToken(request);
       return getLoginUser(token);
    }

    /**
     * 从token中解析用户信息
     * @param token token
     */
    public LoginUser getLoginUser(String token) {
        if (StrUtil.isNotEmpty(token)) {
            JWT jwt;
            try {
                jwt = JWTUtil.parseToken(token);
                String uuid = (String) jwt.getPayload(Constants.JWT_TOKEN_KEY);
                String tokenKey = getTokenKey(uuid);
                return RedisUtils.getCacheObject( tokenKey);
            }catch (Exception e){
                return null;
            }
        }
        return null;
    }

    /**
     * 设置用户登录信息
     * @param loginUser 登录信息
     * @throws Exception 用户登录信息异常
     */
    public void setLoginUser(LoginUser loginUser) throws Exception {
        if (ObjectUtil.isNotEmpty(loginUser)){
            refreshToken(loginUser);
        }else {
            throw new Exception("用户登录信息异常");
        }
    }

    /**
     * 删除用户登录信息
     */
    public void deleteLoginUser(String token) {
        if (StrUtil.isNotEmpty(token)){
            String tokenKey = getTokenKey(token);
            RedisUtils.deleteObject(tokenKey);
        }
    }
    
    

    /**
     * 使用uuid生成token,这个token可以作为RedisKey在redis中保存用户信息
     * @param loginUser 用户登录信息
     * @return token
     */
    public String createToken(LoginUser loginUser) {
        String token = IdUtil.fastUUID();
        loginUser.setToken(token);
        // 补充用户的登录信息
        setUserAgent(loginUser);
        // 刷新token
        refreshToken(loginUser);
        
        //创建token
        Map<String, Object> claims = new HashMap<>();
        claims.put(Constants.JWT_TOKEN_KEY, token);
        return JWTUtil.createToken(claims, getSign());
    }

    /**
     * 验证token,每次都会刷新令牌有效期
     * @param loginUser 用户登录信息
     */
    public boolean verifyToken(LoginUser loginUser) {
        long userExpireTime = loginUser.getExpireTime();
        long currentTime = System.currentTimeMillis();
        
        if (userExpireTime - currentTime <= getExpireTime()){
            refreshToken(loginUser);
            return true;
        }
        return false;
    }


    /**
     * 刷新token, 实现token无感刷新
     * @param loginUser 用户登录信息
     */
    public void refreshToken(LoginUser loginUser) {
        // 刷新登录时间和过期时间
        loginUser.setLoginTime(System.currentTimeMillis());        
        loginUser.setExpireTime(loginUser.getLoginTime() + getExpireTime() );
        // 刷新缓存
        String tokenKey = getTokenKey(loginUser.getToken());
        RedisUtils.setCacheObject(tokenKey, loginUser, Duration.ofMinutes(expireTime));
    }

    /**
     * 设置用户代理信息
     *
     * @param loginUser 登录信息
     */
    public void setUserAgent(LoginUser loginUser)
    {
        UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
        String ip = ServletUtils.getClientIP();
        loginUser
            .setIpaddr(ip)
            .setLoginLocation(AddressUtils.getRealAddressByIP(ip))
            .setBrowser(userAgent.getBrowser().getName())
            .setOs(userAgent.getOs().getName())
            .setLoginTime(System.currentTimeMillis());
    }

    /**
     * 获取请求携带的token
     * @param request 请求对象
     */
    private String getToken(HttpServletRequest request)
    {
        
        // 1. 获得 Token。优先级：Header > Parameter > Sec-Websocket-Protocol
        String token =  getValueWithFallbacks("",
            () -> request.getHeader(header),
            () -> request.getParameter(header),
            //如果是websocket请求
            () -> request.getHeader("Sec-Websocket-Protocol"));
        
        if (token.startsWith(Constants.TOKEN_PREFIX))
        {
            token = token.replace(Constants.TOKEN_PREFIX, "");
        }
        return token;
    }

    private String getTokenKey(String uuid)
    {
        return CacheConstants.ONLINE_TOKEN_KEY + uuid;
    }

    /**
     * 获取token签名
     */
    private byte[] getSign(){
        return secret.getBytes();
    }

    /**
     * 获取过期时间的毫秒值
     * @return 毫秒值
     */
    private int getExpireTime(){
        return expireTime * 60 * 1000;
    }

    
}
